Using Image Compressor Components
This section shows how to use compressors and decompressors in conjunction with the Image Compression Manager.Performing Image Compression
This section describes what the Image Compression Manager does that affects compressors. It then provides sample code that shows how the compressor components prepare for image compression and how to compress an entire image or a horizontal band of an image.When compressing an image, the Image Compression Manager performs three
major tasks:
- The Image Compression Manager first determines which compressor is best able to compress the image. To do so, the Image Compression Manager examines the source image as well as the parameters specified by the application. If the application requested a specific compressor, the Image Compression Manager uses that compressor (unless it is not installed, in which case the Image Compression
Manager returns an error to the application). If the application did not request a compressor, the Image Compression Manager chooses the compressor that will do the best job. The Image Compression Manager collects the information it needs to choose a compressor by issuing theCDPreCompress
request to each qualifying compressor (see page 4-62 for a detailed description of theCDPreCompress
function).- If the chosen compressor can handle the image directly, the Image Compression Manager passes the request through to the compressor. The compressor then processes the image and returns the compressed data to the specified location.
- If none of the compressors can handle it directly, the Image Compression Manager allocates an offscreen buffer and passes image bands to the compressor by issuing a
CDBandCompress
request. (For more on theCDBandCompress
function, see page 4-63.) The compressor processes each band, accumulating the compressed data as it goes. When the image has been completely compressed, the Image Compression Manager returns control to the application.
Choosing a Compressor
Listing 4-1 on page 4-12 shows how the Image Compression Manager calls theCDPreCompress
function before an image is compressed. The compressor component returns information about how it is able to compress the image to the Image Compression Manager, so that it can fit the destination data to the requirements of
the compressor component. This information includes compressor capabilities for
When your compressor component is called with the
- depth of input pixels
- minimum buffer band size
- band increment size
- extension width and height
CDPreCompress
function (described on page 4-62), it can handle all aspects of the function itself, or only the most common ones. All image compressor components must handle at least one case.Here is a list of some of the operations your compressor component can perform during compression. It describes parameters in the compression parameters structure (described on page 4-40) and indicates the operations that are required and which flags in the compressor capabilities flags field of the compressor capabilities structure (described on page 4-35) must be set to allow your compressor to handle them.
Listing 4-1 Preparing for simple compression operations
- Depth conversion. If your compressor component can compress from the pixel depth indicated by the
pixelSize
field (in the pixel map structure pointed to by thesrcPixmap
field of the compression parameters structure), it should set thewantedPixelSize
field of the compressor capability structure to the same value. If it cannot handle that depth, it should specify the closest depth it can support in thewantedPixelSize
field. The Image Compression Manager will convert the source image to that depth.- Extension. If the format for the compressed data is block oriented, the compressor component can request that the Image Compression Manager allocate a buffer that is a multiple of the proper block size by setting the
extendWidth
andextendHeight
parameters of the compressor capability structure. The new pixels are replicated from the left and bottom edges to fill the extended area. If your compressor can perform this extension itself, it should leave theextendWidth
andextendHeight
fields set to 0. In this case, the Image Compression Manager can avoid copying the source image to attain more efficient operation.- Pixel shifting. For pixel sizes less than 8 bits per pixel, it may be necessary to shift the source pixels so that they are at an aligned address. If the
pixelSize
field of the source pixel map structure is less than 8, and your compressor component handles that depth directly, and the left address of the image (srcRect.left - srcPixMap.bounds.left
) is not aligned and your compressor component can handle these pixels directly, then it should set thecodecCanShift
flag in theflags
field of the compressor capabilities structure. If your compressor component does not set this flag, then the data will be copied to a buffer with the image shifted so the first pixel is in the most significant bit of an aligned long-word address.- Updating previous pixel maps. Compressors that perform temporal compression may keep their own copy of the previous frame's pixel map, or they may update the previous frame's pixel map as they perform the compression. In these cases, the compressor component should set the
codecCanCopyPrev
flag if it updates the previous pixel map with the original data from the current frame, or it should set thecodecCanCopyPrevComp
flag if it updates the previous pixel map with a compressed copy of the current frame.
pascal long CDPreCompress (Handle storage, register CodecCompressParams *p) { CodecCapabilities *capabilities = p->capabilities; /* First the compressor returns which depth input pixels it supports based on what the application has available. This compressor can only work with 32-bit input pixels. */ switch ( (*p->imageDescription)->depth ) { case 16: capabilities->wantedPixelSize = 32; break; default: return(codecConditionErr); break; } /* If the buffer gets banded, return the smallest one the compressor can handle. */ capabilities->bandMin = 2; /* If the buffer gets banded, return the increment by which it should increase. */ capabilities->bandInc = 2; capabilities->extendWidth = (*p->imageDescription)->width & 1; capabilities->extendHeight = (*p->imageDescription)->height & 1; /* For efficiency, if the compressor could perform extension, these flags would be set to 0. */ return(noErr); }Compressing a Horizontal Band of an Image
Listing 4-2 shows how the Image Compression Manager calls theCDBandCompress
function when it wants the compressor to compress a horizontal band of an image.
Listing 4-2 Performing simple compression on a horizontal band of an image
- Note
- This example does not perform compression on bands with a bit depth of more than 1 or an extension of width and height. If the example did do so, it would handle these cases faster.
![]()
pascal long CDBandCompress (Handle storage, register CodecCompressParams *p) { short width,height; Ptr cDataPtr,dataStart; short depth; Rect sRect; long offsetH,offsetV; Globals **glob = (Globals **)storage; register char *baseAddr; long numLines,numStrips; short rowBytes; long stripBytes; char mmuMode = 1; register short y; ImageDescription **desc = p->imageDescription; OSErr result = noErr; /* If there is a progress function, give it an open call at the start of this band. */ if (p->progressProcRecord.progressProc) p->progressProcRecord.progressProc (codecProgressOpen, 0, p->progressProcRecord.progressRefCon); width = (*desc)->width; height = (*desc)->height; depth = (*desc)->depth; dataStart = cDataPtr = p->data; /* Figure out offset to first pixel in baseAddr from the pixel size and bounds. */ rowBytes = p->srcPixMap.rowBytes; sRect = p->srcPixMap.bounds; numLines = p->stopLine - p->startLine; /* number of scan lines */ numStrips = (numLines+1)>>1; /* number of strips in */ stripBytes = ((width+1)>>1) * 5; /* Adjust the source baseAddress to be at the beginning of the desired rect. */ switch ( p->srcPixMap.pixelSize ) { case 32: offsetH = sRect.left<<2; break; case 16: offsetH = sRect.left<<1; break; case 8: offsetH = sRect.left; break; /* This compressor does not handle the other cases directly. */ default: result = codecErr; goto bail; } offsetV = sRect.top * rowBytes; baseAddr = p->srcPixMap.baseAddr + offsetH + offsetV; /* If there is not a data-unloading function, adjust the pointer to the next band. */ if ( p->flushProcRecord.flushProc == nil ) { cDataPtr += (p->startLine>>1) * stripBytes; } else { /* Make sure the compressor can deal with the data-unloading function in this case. */ if ( p->bufferSize < stripBytes ) { result = codecSpoolErr; goto bail; } } /* Perform the slower data-loading or progress operation, as required. */ if ( p->flushProcRecord.flushProc || p->progressProcRecord.progressProc ) { SharedGlobals *sg = (*glob)->sharedGlob; for ( y=0; y < numStrips; y++) { SwapMMUMode(&mmuMode); CompressStrip(cDataPtr,baseAddr,rowBytes,width,sg); SwapMMUMode(&mmuMode); baseAddr += rowBytes<<1; if ( p->flushProcRecord.flushProc ) { if ( (result= p->flushProcRecord.flushProc(cDataPtr,stripBytes, p->flushProcRecord.flushRefCon)) != noErr) { result = codecSpoolErr; goto bail; } } else { cDataPtr += stripBytes; } if (p->progressProcRecord.progressProc) { if ( (result= p->progressProcRecord.progressProc) codecProgressUpdatePercent, FixDiv(y,numStrips), p->progressProcRecord.progressRefCon) ) != noErr ) { result = codecAbortErr; goto bail; } } } } else { SharedGlobals *sg = (*glob)->sharedGlob; short tRowBytes = rowBytes<<1; SwapMMUMode(&mmuMode); for ( y=numStrips; y--; ) { CompressStrip(cDataPtr,baseAddr,rowBytes,width,sg); cDataPtr += stripBytes; baseAddr += tRowBytes; } SwapMMUMode(&mmuMode); } }Decompressing an Image
When decompressing an image, the Image Compression Manager performs these three major tasks:
- The Image Compression Manager first determines which decompressor is best able to decompress the image. To do so, the Image Compression Manager examines the source image as well as the parameters specified by the application. If the application requested a specific decompressor, the Image Compression Manager uses that decompressor (unless it is not installed, in which case the Image Compression Manager returns an error to the application). If the application did not request a decompressor, the Image Compression Manager chooses the decompressor that will do the best job. The Image Compression Manager collects the information it needs to choose a decompressor by issuing the
CDPreDecompress
request to each qualifying decompressor (see page 4-63 for a detailed description of theCDPreDecompress
function).- If the chosen decompressor can handle the image directly, the Image Compression Manager passes the request through to the decompressor. The decompressor then processes the image and returns the image to the specified location.
- If none of the decompressors can handle all of the conditions (matrix mapping, masking or matting, depth conversion, and so on) the Image Compression Manager allocates an offscreen buffer and passes image bands to the decompressor at a depth that the decompressor can handle by issuing a
CDBandDecompress
request. (For details on theCDBandDecompress
function, see page 4-64). The decompressor processes each band, building the image as it goes. When the image has been completely decompressed, the Image Compression Manager returns control to the application.
Choosing a Decompressor
Listing 4-3 on page 4-20 provides an example of how a decompressor is chosen. The Image Compression Manager calls theCDPreDecompress
function (described on page 4-63) before an image is decompressed. The decompressor returns information about how it can decompress an image. The Image Compression Manager can fit the destination pixel map to your decompressor's requirements if it is not able to support decompression to the destination directly. The capability information the decompressor returns includes
When your decompressor component is called with the
- depth of pixels for the destination pixel map
- minimum band size handled
- extension width and height required
- band increment size
CDPreDecompress
function, it can handle all aspects of the call itself, or only the most common ones. All decompressors must handle at least one case.This section contains a bulleted list of some of the operations your decompressor component can perform during the decompression operation. The list describes which parameters in the decompression parameters structure (described on page 4-46) indicate the operations are required and which flags in the flags field of the compressor capabilities structure (described on page 4-35) must be set to allow your decompressor to handle them.
For sequences of images the
conditionFlags
field in the decompression parameters structure can be used to determine which parameters may have changed since the last decompression operation. These parameters are also indicated in the bulleted list.Since your decompressor's capabilities depend on the full combination of parameters, it must inspect all the relevant parameters before indicating that it will perform one of the operations itself. For instance, if your decompressor has hardware that can perform scaling only if the destination pixel depth is 32 and there is no clipping, then the pre-decompression operation would have to check the following fields in the decompression parameters structure: the
matrix
field, thepixelSize
field of the destination pixel map structure pointed to by thedestPixMap
field, and themaskBits
fields. Only then could the decompressor decide whether to set thecodecCanScale
flag in thecapabilities
field of the decompression parameters structure.
Listing 4-3 Preparing for simple decompression
- Scaling. The decompressor component can look at the matrix and selectively decide which scaling operations it wishes to handle. If the scaling factor specified by the matrix is not unity and your decompressor can perform the scaling operation, it must set the
codecCanScale
flag in thecapabilities
field. If it does not, then the decompressor is asked to decompress without scaling, and the Image Compression Manager performs the scaling operation afterward.- Depth conversion. If your component can decompress to the pixel depth indicated by the
pixelSize
field (of the pixel map structure pointed to by thedstPixmap
field of the decompression parameters structure), it should set thewantedPixelSize
field of the compressor capability structure to the same value. If it cannot handle that depth, it should specify the closest depth it can handle in thewantedPixelSize
field.- Dithering. When determining whether depth conversion can be performed (for converting an image to a lower bit depth, or to a similar bit depth with a different color table), dithering may be required. This is specified by the dither bit in the
transferMode
field (0x40
) of the decompression parameters structure being set. Theaccuracy
field of the decompression parameters structure indicates whether fast dithering is acceptable (accuracy <= codecNormalQuality
) or whether true error diffusion dithering should be used (accuracy > codecNormalQuality
). Most decompressors do not perform true error diffusion dithering, although they can. When a decompressor cannot perform the dither operation, it should specify the higher bit depth in thewantedPixelSize
field of the compressor capability structure and let the Image Compression Manager perform the depth conversion with dithering. Dithering to 16-bit destinations is normally done only if theaccuracy
field is set to thecodecNormalQuality
value. However, if your decompressor component can perform dithering fast enough, it could be performed at the lower accuracy settings as well. To indicate that your decompressor can perform dithering as specified, it should set thecodecCanTransferMode
flag in thecapabilities
field of the decompression parameters structure.- Color remapping. If the compressed data has an associated color lookup table that is different from the color lookup table of the destination pixel map, then the decompressor can remap the color indices to the closest available ones in the destination itself, or it can let the Image Compression Manager do the remapping. If the decompressor can do the mapping itself, it should set the
codecCanRemap
flag in thecapabilities
flags field of the decompression parameters structure.- Extending. If the format for the compressed data is block-oriented, the decompressor can ask that the Image Compression Manager to allocate a buffer which is a multiple of the proper block size by setting the
extendWidth
andextendHeight
fields of the compressor capabilities structure. If the right and bottom edges of the destination image (as determined by the transformedsrcRect
anddstPixMap.bounds
fields of the decompression parameters structure) are not a multiple of the block size that your decompressor handles, and your decompressor cannot handle partial blocks (writing only the pixels that are needed for blocks that cross the left or bottom edge of the destination), then your decompressor component must set theextendWidth
andextendHeight
fields in the compressor capabilities structure. In this case, the Image Compression Manager creates a buffer large enough so that no partial blocks are needed. Your component can decompress into that buffer. This is then copied to the destination by the Image Compression Manager. Your component can avoid this extra step if it can handle partial blocks. In this case, it should leave theextendWidth
andextendHeight
fields set to 0.- Clipping. If clipping must be performed on the image to be decompressed, the
maskBits
field of the decompression parameters structure is nonzero. In theCDPreDecompress
function, it will be a region handle to the actual clipping region. If your decompressor can handle the clipping operation as specified by this region, it should set thecodecCanMask
bit in thecapabilities
flags field of the decompression parameters structure. If it does this, then the parameter passed to theCDBandDecompress
function in themaskBits
field will be a bitmap instead of a region. If desired, your decompressor can save a copy of the actual region structure during the pre-decompression operation.- Matting. If a matte must be applied to the decompressed image, the
transferMode
field of the decompression parameters structure is set to blend and themattePixMap
field is a handle to the pixel map to be used as the matte. If your decompressor can perform the matte operation, then it should set thecodecCanMatte
field in the compressor capabilities structure. If it does not, then the Image Compression Manager will perform the matte operation after the decompression is complete.- Pixel shifting. For pixel sizes less than 8 bits per pixel, it may be necessary to shift
the destination pixels so that they are at an aligned address. If the pixel size of the destination pixel map is less than 8 and your component handles that depth directly, and the left address of the image is not aligned and your component can handle these pixels directly, then it should set thecodecCanShift
flag in thecapabilities
field of the decompression parameters structure. If your component does not set this flag, the Image Compression Manager allocates a buffer for and performs the shifting after the decompression is completed.- Partial extraction. If the source rectangle is not the entire image and the component can decompress only the part of the image specified by the source rectangle, it should set the
codecCanSrcExtract
flag in thecapabilities
field of the decompression parameters structure. If it does not, the Image Compression Manger asks the component to decompress the entire image and copy only the required part to the destination.
pascal long CDPreDecompress(Handle storage, register CodecDecompressParams *p) { register CodecCapabilities*capabilities = p->capabilities; RectdRect = p->srcRect; /* Check if the matrix is OK for this decompressor. This decompressor doesn't do anything fancy. */ if ( !TransformRect(p->matrix,&dRect,nil) ) return(codecConditionErr); /* Decide which depth compressed data this decompressor can deal with. */ switch ( (*p->imageDescription)->depth ) { case 16: break; default: return(codecConditionErr); break; } /* This decompressor can deal only with 32-bit pixels. */ capabilities->wantedPixelSize = 32; /* The smallest possible band the decompressor can handle is 2 scan lines. */ capabilities->bandMin = 2; /* This decompressor can deal with 2 scan line high bands. */ capabilities->bandInc = 2; /* If this decompressor needed its pixels be aligned on some integer multiple, you would set extendWidth and extendHeight to the number of pixels by which you need the destination extended. If you don't have such requirements or if you take care of them yourself, you set extendWidth and extendHeight to 0. */ capabilities->extendWidth = p->srcRect.right & 1; capabilities->extendHeight = p->srcRect.bottom & 1; return(noErr); }Decompressing a Horizontal Band of an Image
Listing 4-4 shows how to decompress the horizontal band of an image. The Image Compression Manager calls theCDBandDecompress
function when it wants a decompressor to decompress an image or a horizontal band of an image. The pixel data indicated by thebaseAddr
field is guaranteed to conform to the criteria your decompressor specified in theCDPreDecompress
function.
Listing 4-4 Performing a decompression operation
- Note
- This example does not perform decompression on bands with a bit depth of more than one or an extension of width and height. If the example did do so, it would handle these cases faster.
![]()
pascal long CDBandDecompress(Handle storage,register CodecDecompressParams *p) { Rect dRect; long offsetH,offsetV; Globals **glob = (Globals **)storage; long numLines,numStrips; short rowBytes; long stripBytes; short width; register short y; register char* baseAddr; char *cDataPtr; char mmuMode = 1; OSErr result = noErr; /* Calculate the real base address based on the boundary rectangle. If it's not a linear transformation, this decompressor does not perform the operation. */ dRect = p->srcRect; if ( !TransformRect(p->matrix,&dRect,nil) ) return(paramErr); /* If there is a progress function, give it an open call at the start of this band. */ if (p->progressProcRecord.progressProc) p->progressProcRecord.progressProc(codecProgressOpen,0, p->progressProcRecord.progressRefCon); /* Initialize some local variables. */ width = (*p->imageDescription)->width; rowBytes = p->dstPixMap.rowBytes; numLines = p->stopLine - p->startLine; /* number of scan lines in this band */ numStrips = (numLines+1)>>1; /* number of strips in this band */ stripBytes = ((width+1)>>1) * 5; /* number of bytes in 1 strip of blocks */ cDataPtr = p->data; /* Adjust the destination base address to be at the beginning of the desired rectangle. */ offsetH = (dRect.left - p->dstPixMap.bounds.left); switch ( p->dstPixMap.pixelSize ) { case 32: offsetH <<=2; /* 1 pixel = 4 bytes */ break; case 16: offsetH <<=1; /* 1 pixel = 2 bytes */ break; case 8: break; /* 1 pixel = 1 byte */ default: result = codecErr; /* This decompressor doesn't handle these cases, although it could. */ goto bail; } offsetV = (dRect.top - p->dstPixMap.bounds.top) * rowBytes; baseAddr = p->dstPixMap.baseAddr + offsetH + offsetV; /* If your decompressor component is skipping some data, it just skips it here. You can tell because firstBandInFrame indicates this is the first band for a new frame, and if startLine is not 0, then that many lines were clipped out. */ if ( (p->conditionFlags & codecConditionFirstBand) && p->startLine != 0 ) { if ( p->dataProcRecord.dataProc ) { for ( y=0; y < p->startLine>>1; y++ ) { if ( (result=p->dataProcRecord.dataProc (&cDataPtr,stripBytes, p->dataProcRecord.dataRefCon)) != noErr ) { result = codecSpoolErr; goto bail; } cDataPtr += stripBytes; } } else cDataPtr += (p->startLine>>1) * stripBytes; } /* If there is a data-loading function spooling the data to your decompressor, then you have to decompress the data in the chunk size that is specified, or, if there is a progress function, you must make sure to call it as you go along. */ if ( p->dataProcRecord.dataProc || p->progressProcRecord.progressProc ) { SharedGlobals *sg = (*glob)->sharedGlob; for (y=0; y < numStrips; y++) { if (p->dataProcRecord.dataProc) { if ( (result=p->dataProcRecord.dataProc (&cDataPtr,stripBytes, p->dataProcRecord.dataRefCon)) != noErr ) { result = codecSpoolErr; goto bail; } } SwapMMUMode(&mmuMode); DecompressStrip(cDataPtr,baseAddr,rowBytes,width,sg); SwapMMUMode(&mmuMode); baseAddr += rowBytes<<1; cDataPtr += stripBytes; if (p->progressProcRecord.progressProc) { if ( (result=p->progressProcRecord.progressProc (codecProgressUpdatePercent, FixDiv(y, numStrips), p->progressProcRecord.progressRefCon)) != noErr ) { result = codecAbortErr; goto bail; } } } /* Otherwise, do the fast case. */ } else { SharedGlobals *sg = (*glob)->sharedGlob; shorttRowBytes = rowBytes<<1; SwapMMUMode(&mmuMode); for ( y=numStrips; y--; ) { DecompressStrip(cDataPtr,baseAddr,rowBytes,width,sg); baseAddr += tRowBytes; cDataPtr += stripBytes; } SwapMMUMode(&mmuMode); } /* IMPORTANT-- Update the pointer to data in the decompression parameters structure, so that when your decompressor gets the next band, you'll be at the right place in your data. */ p->data = cDataPtr; if ( p->conditionFlags & codecConditionLastBand ) { /* Tie up any loose ends on the last band of the frame. */ } bail: /* If there is a progress function, give it a close call at the end of this band. */ if (p->progressProcRecord.progressProc) p->progressProcRecord.progressProc(codecProgressClose,0, p->progressProcRecord.progressRefCon); return(result); }
Subtopics
- Performing Image Compression
- Choosing a Compressor
- Compressing a Horizontal Band of an Image
- Decompressing an Image
- Choosing a Decompressor
- Decompressing a Horizontal Band of an Image
Main | Top of Section | What's New | Apple Computer, Inc. | Find It | Feedback | Help